package xyz.scootaloo.kami.server.service.impl

import io.vertx.core.Future.failedFuture
import io.vertx.kotlin.coroutines.await
import xyz.scootaloo.kami.server.model.*
import xyz.scootaloo.kami.server.model.AccessType.*
import xyz.scootaloo.kami.server.model.dao.FileDAO
import xyz.scootaloo.kami.server.model.dao.UserDAO
import xyz.scootaloo.kami.server.service.*
import xyz.scootaloo.kami.server.service.RuleSystemService.RuleItem
import xyz.scootaloo.kami.server.standard.AccessViolationException
import xyz.scootaloo.kami.server.standard.UploadExceedLimitException
import xyz.scootaloo.kami.server.standard.getLogger
import xyz.scootaloo.kami.server.verticle.runCoroutineOnWorkerContext
import java.io.FileNotFoundException
import java.nio.file.NotDirectoryException

/**
 * @author flutterdash@qq.com
 * @since 2022/1/11 15:54
 */
object InternalFileSystemViewServiceImpl : FileSystemViewService, ApplicationStageListener {
    private val log = getLogger()

    private val fileResolver = FileResolver()
    private val ruleService = RuleSystemService()
    private val uploadService = UploadService()
    private val downloadService = DownloadService()
    private val logService = FileLogService()

    override fun afterApplicationStarted() {
        runCoroutineOnWorkerContext {
            val paths = ruleService.listCapLimitPaths().await()
            val cacheMap = HashMap<String, Long>()
            for (path in paths) {
                val used = fileResolver.calculateDirStorageSpace(path, cacheMap)
                cacheMap[fileResolver.realPathString(path)] = used
                ruleService.updateCapAvailable(path, used)
                log.info("更新在`${path}`上的已用空间, 已使用${fileResolver.humanReadableSize(used)}")
            }
        }
    }

    override suspend fun listDirectoryContent(token: AccessToken): List<EFile> {
        assertAccessViolation(token.path)
        ruleService.access((token + ACCESS)).await()
        return ruleService.access((token + ACCESS)) { rule ->
            fileResolver.listDirContents(token.path) { subItem ->
                listContentFilter(token.level, rule, subItem)
            }
        }.await()
    }

    override suspend fun showFileDesc(token: AccessToken): FileDetails {
        assertAccessViolation(token.path)
        return ruleService.accessWithCoroutine((token + ACCESS)) {
            DTOUtil.fileDesc(token.path)
        }
    }

    override suspend fun createFileDownloadLink(token: AccessToken, ipAddress: String): String {
        assertAccessViolation(token.path)
        return ruleService.access((token + DOWNLOAD)).compose {
            val exists = fileResolver.isExists(token.path)
            if (exists) {
                failedFuture(FileNotFoundException(token.path))
            } else {
                downloadService.createDownloadLink(token, ipAddress)
            }
        }.await()
    }

    override suspend fun createFile(token: AccessToken, filename: String) {
        assertAccessViolation(token.path)
        ruleService.accessWithCoroutine((token + ACCESS + MODIFY)) { dir ->
            dir.assertAllow(token.level, MODIFY)
            fileResolver.createFile(token.path, filename).await()
            logService.onFileCreate(token.uid(), token.path, filename)
        }
    }

    override suspend fun createDirectory(token: AccessToken, dirname: String) {
        assertAccessViolation(token.path)
        ruleService.accessWithCoroutine((token + ACCESS + MODIFY)) { dir ->
            dir.assertAllow(token.level, MODIFY)
            fileResolver.createDir(token.path, dirname).await()
            logService.onDirectoryCreate(token.uid(), token.path, dirname)
        }
    }

    override suspend fun renameFile(token: AccessToken, filename: String, newFilename: String) {
        assertAccessViolation(token.path)
        ruleService.accessWithCoroutine((token + ACCESS + MODIFY)) {
            fileResolver.rename(token.path, filename, newFilename).await()
            logService.onFileRename(token.uid(), token.path, filename, newFilename)
        }
    }

    override suspend fun moveFile(token: AccessToken, filename: String, destDir: String) {
        assertAccessViolation(token.path)
        assertAccessViolation(destDir)
        ruleService.access((token + ACCESS + MODIFY)).compose {
            ruleService.accessAsync((AccessToken(destDir, token.level) + ACCESS + MODIFY)) {
                fileResolver.move(token.path, filename, destDir).onSuccess {
                    logService.onFileMove(token.uid(), token.path, filename, destDir)
                }
            }
        }.await()
    }

    override suspend fun deleteFile(token: AccessToken, filename: String) {
        ruleService.access((token + ACCESS + MODIFY)).compose {
            val fullPath = fileResolver.buildFilepath(token.path, filename)
            val fileInfo = fileResolver.resolveFile(fullPath)
            fileResolver.deleteFile(token.path, filename).onSuccess {
                logService.onFileDelete(token.uid(), token.path, filename)
                if (fileInfo.size > 0) {
                    ruleService.releaseCap(fullPath, fileInfo.size)
                }
            }
        }.await()
    }

    override suspend fun unzipFile(token: AccessToken, filename: String) {
        TODO("Not yet implemented")
    }

    override suspend fun zipFile(token: AccessToken, filename: String) {
        TODO("Not yet implemented")
    }

    override suspend fun zipFiles(token: AccessToken, files: List<String>) {
        TODO("Not yet implemented")
    }

    override suspend fun zipDirectory(token: AccessToken) {
        TODO("Not yet implemented")
    }

    override suspend fun checkUploadPerm(token: AccessToken) {
        assertAccessViolation(token.path)
        ruleService.access((token + ACCESS + MODIFY)).await()
    }

    override fun asyncDeleteFileAndUpdateCap(basePath: String, filename: String) {
        Async.run {
            val fullPath = fileResolver.buildFilepath(basePath, filename)
            val fileInfo = fileResolver.resolveFile(fullPath) // 因为目标文件不存在而中断流程
            fileResolver.deleteFile(basePath, filename).onSuccess {
                if (fileInfo.size > 0) {
                    ruleService.releaseCap(fullPath, fileInfo.size)
                }
            }
        }
    }

    override suspend fun createUploadTask(
        uploader: Int,
        token: AccessToken,
        files: List<SingleUploadFileInfo>
    ): UploadFileResponse {
        val path = token.path
        assertAccessViolation(path)
        assertIsDirectory(path)

        try {
            val totalSize = files.sumOf { it.filesize }
            ruleService.tryShrinkageCap(token.path, totalSize).await()
        } catch (e: UploadExceedLimitException) {
            return UploadFileResponse.error("此路径容量不足", e.reporter)
        } catch (otherException: Throwable) {
            throw otherException
        }

        val actualFiles = fileResolver.createTmpUploadFiles(path, files)
        return uploadService.createUploadTask(uploader, path, actualFiles)
    }

    override suspend fun displayDirectoryCap(token: AccessToken): DirectoryCapacity {
        val node = ruleService.showCap(token.path).await()
        return DirectoryCapacity(node.fullPath(), node.capacityLimit(), node.capacityAvailable())
    }

    private fun listContentFilter(userLevel: Int, node: RuleItem, subFilename: String): Boolean {
        if (node.containsSubItem(subFilename)) {
            return node.subItem(subFilename).isAllow(userLevel, ACCESS)
        }
        return true
    }

    private fun assertAccessViolation(path: String) {
        if ("../" in path)
            throw AccessViolationException(path)
    }

    private fun assertIsDirectory(path: String) {
        if (!fileResolver.isDirectory(path))
            throw NotDirectoryException(path)
    }

    object DTOUtil {
        @Throws(FileNotFoundException::class)
        suspend fun fileDesc(filename: String): FileDetails {
            val file = fileResolver.resolveFile(filename)
            val record = FileDAO.find(filename) ?: SysFile.create(-1, filename)
            val rule = ruleService.showCap(filename).await()
            val limit = ruleService.showRule(filename).await()

            return FileDetails(
                desc = file,
                record = uploadRecord(record),
                rule = ruleDesc(rule),
                cap = limitDesc(limit)
            )
        }
    }

    private fun uploadRecord(record: SysFile): UploaderRecord {
        val uploader = UserDAO.findByUid(record.owner)?.username ?: "unknown"
        return UploaderRecord(record.owner, uploader, record.remark, record.download)
    }

    private fun ruleDesc(rule: RuleSystemService.LowRuleItem): FileRuleInfo {
        return FileRuleInfo(rule.fullPath(), rule.rule())
    }

    private fun limitDesc(limit: RuleSystemService.LowRuleItem): FileCapInfo {
        return FileCapInfo(limit.fullPath(), limit.capacityLimit(), limit.capacityAvailable())
    }
}